#include "Matrix.h"
#include "Shader.h"

#include <iostream>



CShader::CShader(void)
{
	_uProgram			= 0;
	_uVertexShader		= 0;
	_uFragmentShader	= 0;
}

CShader::~CShader(void)
{
}

void CShader::CreateShader()
{
	_uProgram = glCreateProgram();		CHECK_GL_ERROR();

#ifdef _DEBUG
	_bDoneValidation	= false;
#endif // _DEBUG
}

bool CShader::AttachShader(const string& sShaderSrcFileName_, const ShaderType& eShaderType_)
{
	int nState = 0;
	GetShaderSource(sShaderSrcFileName_, eShaderType_);
	nState += CompileShader(eShaderType_); // Compiles shader and attaches to program

	return nState==2;
}

void CShader::GetShaderSource(const string& sShaderSrcFileName_, const ShaderType& eShaderType_)
{
	// open shader file
	_fShaderFile.OpenFileRead(sShaderSrcFileName_);

	// Read the source code
	switch (eShaderType_)
	{
	case Vertex:
		_fShaderFile.ReadFile(&_sVertexShaderSource);
//#if _DEBUG
//		printf("Shader source:\n%s\n", _sVertexShaderSource.c_str());
//#endif
		break;
	case Fragment:
		_fShaderFile.ReadFile(&_sFragmentShaderSource);
//#if _DEBUG
//		printf("Shader source:\n%s\n", _sFragmentShaderSource.c_str());
//#endif
		break;
	}

	// close file
	_fShaderFile.CloseFile();

	return;
}

bool CShader::CompileShader(const ShaderType& eShaderType_ )
{
	GLuint* pShader; // pointer to either Vertex or Fragment shader object, assigned in switch below
	GLint bCompiled = 0;
	const char* pSource;

	// create shader
	switch (eShaderType_)
	{
	case Vertex:
		pShader = &_uVertexShader;
		pSource = _sVertexShaderSource.c_str();
		*pShader = glCreateShader(GL_VERTEX_SHADER);							CHECK_GL_ERROR();
		break;
	case Fragment:
		pShader = &_uFragmentShader;
		pSource = _sFragmentShaderSource.c_str();
		*pShader = glCreateShader(GL_FRAGMENT_SHADER);							CHECK_GL_ERROR();
		break;
	}

	// attach source
	glShaderSource(*pShader, 1, &pSource, NULL);							CHECK_GL_ERROR();
	
	// compile shader
	glCompileShader(*pShader);													CHECK_GL_ERROR();
	glGetShaderiv(*pShader, GL_COMPILE_STATUS, &bCompiled );					CHECK_GL_ERROR();

	// if debugging, always show compile log
#ifdef _DEBUG
	PrintShaderInfoLog(*pShader);
#endif
	if (bCompiled)
	{
		// shader compiled, attach to program
		glAttachShader(_uProgram, *pShader);									CHECK_GL_ERROR();
		return true;
	}
	else
	{
		PrintShaderInfoLog(*pShader);
		glDeleteShader(*pShader);
		*pShader = 0;
		return false;
	}
}

bool CShader::LinkShader()
{
	GLint bLinked = 0;

	// check if program exists
	if (!_uProgram)
		throw exception("No shader program created");

	// link program
	glLinkProgram (_uProgram);												CHECK_GL_ERROR();
	glGetProgramiv(_uProgram, GL_LINK_STATUS, &bLinked );                   CHECK_GL_ERROR();
#ifdef _DEBUG
	PrintProgramInfoLog();
#endif

	if (bLinked)
	{
		return true;
	}
	else
	{
		PrintProgramInfoLog();
		return false;
	}
}

void CShader::GetUniInfo()
{
	// get info on parameters, for debugging
	int nPars,nMaxLength;
	glGetProgramiv(_uProgram,GL_ACTIVE_UNIFORMS,&nPars);
	glGetProgramiv(_uProgram,GL_ACTIVE_UNIFORM_MAX_LENGTH,&nMaxLength);

	if (nPars>0)
	{
		cout << "Active Uniforms (program id " << _uProgram << "):" << endl;
		cout << "index" << '\t' << "name" << endl;
		for (int nPar=0;nPar<nPars;nPar++)
		{
			GLchar* buffer;
			buffer = (GLchar*) malloc(nMaxLength);
			if (buffer == NULL)
			{
				printf("ERROR: Could not allocate InfoLog buffer\n");
			}
			else
			{
				GLsizei length;
				GLint vsize;
				GLenum type;

				glGetActiveUniform(_uProgram,nPar,100,&length,&vsize,&type,buffer);
				cout << nPar << '\t' << buffer << endl;
				free(buffer);
			}
		}
	}
}

void CShader::GetAttribInfo()
{
	// get info on parameters, for debugging
	int nPars,nMaxLength;
	glGetProgramiv(_uProgram,GL_ACTIVE_ATTRIBUTES,&nPars);
	glGetProgramiv(_uProgram,GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,&nMaxLength);

	if (nPars>0)
	{
		cout << "Active attributes (program id " << _uProgram << "):" << endl;
		cout << "index" << '\t' << "name" << endl;
		for (int nPar=0;nPar<nPars;nPar++)
		{
			GLchar* buffer;
			buffer = (GLchar*) malloc(nMaxLength);
			if (buffer == NULL)
			{
				printf("ERROR: Could not allocate InfoLog buffer\n");
			}
			else
			{
				GLsizei length;
				GLint vsize;
				GLenum type;

				glGetActiveAttrib(_uProgram,nPar,100,&length,&vsize,&type,buffer);
				cout << nPar << '\t' << buffer << endl;
				free(buffer);
			}
		}
	}
}

void CShader::PrintShaderInfoLog(const GLuint& uShader_)
{
	int infologLength = 0;
	int charsWritten  = 0;
	GLchar *infoLog;

	CHECK_GL_ERROR();  // Check for OpenGL errors

	glGetShaderiv(uShader_, GL_INFO_LOG_LENGTH, &infologLength);

	CHECK_GL_ERROR();  // Check for OpenGL errors

	if (infologLength > 1) // apparently log's minimum length is 1, which is a useless log
	{
		infoLog = (GLchar *)malloc(infologLength);
		if (infoLog == NULL)
		{
			printf("ERROR: Could not allocate InfoLog buffer\n");
		}
		else
		{
			glGetShaderInfoLog(uShader_, infologLength, &charsWritten, infoLog);
			printf("Shader InfoLog:\n%s\n\n", infoLog);
			free(infoLog);
		}
	}
	CHECK_GL_ERROR();  // Check for OpenGL errors
}

void CShader::PrintProgramInfoLog()
{
	int infologLength = 0;
	int charsWritten  = 0;
	GLchar *infoLog;

	CHECK_GL_ERROR();  // Check for OpenGL errors

	glGetProgramiv(_uProgram, GL_INFO_LOG_LENGTH, &infologLength);

	CHECK_GL_ERROR();  // Check for OpenGL errors

	if (infologLength > 1) // apparently log's minimum length is 1, which is a useless log
	{
		infoLog = (GLchar *)malloc(infologLength);
		if (infoLog == NULL)
		{
			printf("ERROR: Could not allocate InfoLog buffer\n");
		}
		else
		{
			glGetProgramInfoLog(_uProgram, infologLength, &charsWritten, infoLog);
			printf("Program InfoLog:\n%s\n\n", infoLog);
			free(infoLog);
		}
	}
	CHECK_GL_ERROR();  // Check for OpenGL errors
}

GLint CShader::GetUniLoc(const GLchar *pName_)
{
	GLint loc = glGetUniformLocation(_uProgram, pName_);

	if (loc == -1)
		printf("No uniform named \"%s\"\n", pName_);

	CHECK_GL_ERROR();  // Check for OpenGL errors
	return loc;
}

GLint CShader::GetAttribLoc(const GLchar *pName_)
{
	GLint loc = glGetAttribLocation(_uProgram, pName_);

	if (loc == -1)
		printf("No attribute named \"%s\"\n", pName_);

	CHECK_GL_ERROR();  // Check for OpenGL errors
	return loc;
}

bool CShader::UseShader()
{
	if (_uProgram!=0)
	{
#ifdef _DEBUG
		if (!_bDoneValidation) // if in debug mode, for first run, Validate program and display program log
		{
			GLint bValidated;
			glValidateProgram(_uProgram);	CHECK_GL_ERROR();
			PrintProgramInfoLog();
			glGetProgramiv(_uProgram, GL_VALIDATE_STATUS, &bValidated);
			if (bValidated==1)
			{
				glUseProgram(_uProgram);	CHECK_GL_ERROR();
				_bDoneValidation = true;
				return true;
			} 
			else
				throw exception("Shader program did not validate");
		}
		else
		{
#endif // _DEBUG
			glUseProgram(_uProgram);	CHECK_GL_ERROR();
			return true;
#ifdef _DEBUG
		}
#endif // _DEBUG
	} 
	else
	{
		// no shader program
		printf("No shader program");
		return false;
	}
}

void CShader::DisableShader()
{
	glUseProgram(0);			CHECK_GL_ERROR();
}

void CShader::DeleteShader()
{
	if (_uProgram!=0 && glIsProgram(_uProgram)) // nothing to delete if no program
	{
		// tag shaders for deletion, will be deleted when program is deleted
		if (_uVertexShader!=0   && glIsShader(_uVertexShader))
		{
			glDeleteShader(_uVertexShader);		CHECK_GL_ERROR();
			_uVertexShader = 0;
			_sVertexShaderSource.clear();
		}
		if (_uFragmentShader!=0 && glIsShader(_uFragmentShader))
		{
			glDeleteShader(_uFragmentShader);	CHECK_GL_ERROR();
			_uFragmentShader = 0;
			_sFragmentShaderSource.clear();
		}

		// Disable shader to be sure its not in use
		DisableShader();

		// delete shader program (shader objects will also be deleted as they will be detached)
		glDeleteProgram(_uProgram);				CHECK_GL_ERROR();
		_uProgram = 0;
	}
}


// not class member
#ifdef _DEBUG
void CheckGLError(char *file, int line)
{
	GLenum glErr;

	glErr = glGetError();
	while (glErr != GL_NO_ERROR) 
	{
		const GLubyte* sError = gluErrorString(glErr);

		if (sError)
			cout << "GL Error #" << glErr << "(" << gluErrorString(glErr) << ") " << " in File " << file << " at line: " << line << endl;
		else
			cout << "GL Error #" << glErr << " (no message available)" << " in File " << file << " at line: " << line << endl;

		glErr = glGetError();
	}
}
#endif // _DEBUG
